package com.gframework.util;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import org.springframework.lang.Nullable;

/**
 * <strong>null安全的/负时间戳安全的 </strong>日期工具类.
 * <ul>
 * <li>获取当前环境的TimeZone {@link DateUtils#DEFAULT_TIME_ZONE}</li>
 * <li>获取当前环境的ZoneOffset {@link DateUtils#DEFAULT_TIME_ZONE}</li>
 * <li><strong>toDate {@code -} </strong>LocalDate/LocalDateTime转Date</li>
 * <li><strong>toLocalDate {@code -} </strong>Date转LocalDate/LocalDateTime</li>
 * <li><strong>toLocalDateTime {@code -} </strong>Date转LocalDate/LocalDateTime</li>
 * <li><strong>getCurrentMonthFirstDate/getCurrentMonthLastDay {@code -} </strong>获取当前月份第一天/最后一天</li>
 * <li><strong>min/max {@code -} </strong>比较两个Date取得较小的/较大的那个</li>
 * <li><strong>truncTime {@code -} </strong>去掉一个日期的时、分、秒、毫秒部分（变为0）</li>
 * </ul>
 * 
 * @since 2.0.0
 * @author Ghwolf
 */
public class DateUtils {

	public static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
	public static final ZoneOffset DEFAULT_ZONE_OFFSET = ZoneOffset.ofTotalSeconds(DEFAULT_TIME_ZONE.getRawOffset() / 1000);
	
	private DateUtils(){
	}

	/**
	 * LocalDate转Date
	 * @param localDate 要转换的localDate，可以为null
	 * @return 转换后的Date对象，如果localDate为null则返回null
	 */
	@Nullable
	public static Date toDate(@Nullable LocalDate localDate) {
		return toDate(localDate,DEFAULT_ZONE_OFFSET);
	}
	
	/**
	 * LocalDate转Date
	 * @param localDate 要转换的localDate，可以为null
	 * @param zoneOffset 当前时区偏移量，如果为null，则使用默认的ZoneOffset
	 * @return 转换后的Date对象，如果localDate为null则返回null
	 */
	@Nullable
	public static Date toDate(@Nullable LocalDate localDate,@Nullable ZoneOffset zoneOffset) {
		if (localDate == null) return null ;
		if (zoneOffset == null) zoneOffset = DEFAULT_ZONE_OFFSET;
		return new Date(localDate.atStartOfDay().toEpochSecond(zoneOffset) * 1000);
	}
	
	/**
	 * LocalDateTime转Date
	 * @param localDateTime 要转换的localDateTime，可以为null
	 * @return 转换后的Date对象，如果localDateTime为null则返回null
	 */
	@Nullable
	public static Date toDate(@Nullable LocalDateTime localDateTime) {
		return toDate(localDateTime,DEFAULT_ZONE_OFFSET);
	}
	
	/**
	 * LocalDateTime转Date
	 * @param localDateTime 要转换的localDateTime，可以为null
	 * @param zoneOffset 当前时区偏移量，如果为null，则使用默认的ZoneOffset
	 * @return 转换后的Date对象，如果localDateTime为null则返回null
	 */
	@Nullable
	public static Date toDate(@Nullable LocalDateTime localDateTime,@Nullable ZoneOffset zoneOffset) {
		if (localDateTime == null) return null ;
		if (zoneOffset == null) zoneOffset = DEFAULT_ZONE_OFFSET;
		return new Date(localDateTime.toEpochSecond(zoneOffset) * 1000);
	}

	/**
	 * 将一个date转换为LocalDate类对象
	 * @param date 要转换的Date类对象，可以为空
	 * @param zoneId 时区时间偏移量，如果为null，则会使用当前时区
	 * @return 如果date为null则返回null，否则返回LocalDate类对象
	 */
	@Nullable
	public static LocalDate toLocalDate(@Nullable Date date,ZoneId zoneId) {
		if (date == null) return null ;
		return toLocalDateTime(date, zoneId).toLocalDate();
	}
	
	/**
	 * 将一个date转换为LocalDate类对象（按当前时区处理）
	 * @param date 要转换的Date类对象，可以为空
	 * @return 如果date为null则返回null，否则返回LocalDate类对象
	 */
	@Nullable
	public static LocalDate toLocalDate(@Nullable Date date) {
		return toLocalDate(date, DEFAULT_ZONE_OFFSET);
	}
	
	/**
	 * 将一个date转换为LocalDateTime类对象
	 * @param date 要转换的Date类对象，可以为空
	 * @param zoneId 时区时间偏移量，如果为null，则会使用当前时区
	 * @return 如果date为null则返回null，否则返回LocalDateTime类对象
	 */
	@Nullable
	public static LocalDateTime toLocalDateTime(@Nullable Date date,ZoneId zoneId) {
		if (date == null) return null ;
		return LocalDateTime.ofInstant(date.toInstant(),zoneId == null ? DEFAULT_ZONE_OFFSET : zoneId);
	}
	
	/**
	 * 将一个date转换为LocalDateTime类对象（按当前时区处理）
	 * @param date 要转换的Date类对象，可以为空
	 * @return 如果date为null则返回null，否则返回LocalDateTime类对象
	 */
	@Nullable
	public static LocalDateTime toLocalDateTime(@Nullable Date date) {
		return toLocalDateTime(date,DEFAULT_ZONE_OFFSET);
	}
	
	/**
	 * 获取当前月的第一天日期
	 * @return 返回当前月第一天的Date对象
	 */
	public static Date getCurrentMonthFirstDate() {
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.MONTH, calendar.getActualMinimum(Calendar.MONTH));
		calendar.set(Calendar.HOUR, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}

	/**
	 * 获取当前月的最后一天日期
	 * @return 返回当前月最后一天的Date对象
	 */
	public static Date getCurrentMonthLastDay() {
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.MONTH, calendar.getActualMaximum(Calendar.MONTH));
		calendar.set(Calendar.HOUR, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}
	
	/**
	 * 比较两个date，返回较小的那个，如果存在null，则返回不是null的那个，如果都是null，则返回null
	 * @param date1 第一个日期，可以为null
	 * @param date2 第二个日期，可以为null
	 * @return 返回较小的或不是null那个日期，如果都是null则返回null
	 */
	@Nullable
	public static Date min(@Nullable Date date1,@Nullable Date date2){
		if (date1 == null) {
			return date2 ;
		} else if (date2 == null) {
			return date1 ;
		}
		return date1.compareTo(date2) <= 0 ? date1 : date2 ;
	}
	
	/**
	 * 比较两个date，返回较大的那个，如果存在null，则返回不是null的那个，如果都是null，则返回null
	 * @param date1 第一个日期，可以为null
	 * @param date2 第二个日期，可以为null
	 * @return 返回较大的或不是null那个日期，如果都是null则返回null
	 */
	@Nullable
	public static Date max(@Nullable Date date1,@Nullable Date date2){
		if (date1 == null) {
			return date2 ;
		} else if (date2 == null) {
			return date1 ;
		}
		return date1.compareTo(date2) <= 0 ? date2 : date1 ;
	}
	

	/**
	 * 根据指定的时区偏移量，截断一个日期的时分秒部分.
	 * <p>既时分秒毫秒都变成0
	 * 
	 * @param date 日期对象，如果为null，则返回null
	 * @param timeZone 时区对应的时间偏移量对象，如果为null，则使用当前所属时区的偏移量
	 * @return 返回截断后的日期
	 */
	@Nullable
	public static Date truncTime(@Nullable Date date,@Nullable TimeZone timeZone) {
		if (date == null) return null ;
		if (timeZone == null) timeZone = DEFAULT_TIME_ZONE;
		long t = date.getTime();
		long subTimestamp = ((t + timeZone.getRawOffset()) % 86400000);
		if (subTimestamp > 0) {
			t = t - subTimestamp;
		} else if (subTimestamp != 0) {
			// 兼容负时间戳
			t = t - (86400000 + subTimestamp);
		}
		return new Date(t);
	}
	
	/**
	 * 截断一个日期的时分秒部分，会根据当前所在时区的偏移量进行截断.
	 * 
	 * @param date 日期对象，如果为null，则返回null
	 * @return 返回截断后的日期
	 */
	@Nullable
	public static Date truncTime(@Nullable Date date) {
		return truncTime(date,DEFAULT_TIME_ZONE);
	}
	
}
